Prerequisites

Loading the required packages

library(tidyverse)
library(dplyr)
library(ggplot2)
library(rtweet)
library(readr)
library(DataExplorer)
library(ggcute)

Dataset

Import processed data, which can be found here.

#read preprocessed data
wines <- read.csv(file = '../data/processed_data/wines.csv')

Get sample of dataset

#set seed value to birthday of Ricardo Rodriguez, American wrestler and ring announcer and Dr. Reinaldo (Rei) Sanchez-Arias
set.seed(19630217)

#set percentage to test with for simplicity, if needed
percentage <- 5
wine_sample<- sample_n(wines, percentage/100*nrow(wines))

Split Taster data into different Data Frame

tasters <- wines %>%
  select(taster_name, taster_twitter_handle) %>% unique()
tasters

Drop taster_twitter_handle in wines dataframe

wines <- wines %>%
  select(-taster_twitter_handle)
head(wines)

Add Reviewer profile info

Each reviewer has there own bias. To offset that we made a “profile” for each reviewer which includes characteristics like: avg_points, sd_points, and var_points

taster_rating_profile <- wines %>%
  group_by(taster_name) %>%
  summarize(
    avg_points = mean(points),
    sd_points = sd(points),
    var_points = var(points),
    reviews = n()
  )

tasters <- inner_join(tasters, taster_rating_profile, by = "taster_name")
head(tasters)

Add Rating Classification

Add following classification to wine dataset as found on the website:

Category Rating Description
Classic 98-100 The pinnacle of quality.
Superb 94-97 A great achievement.
Excellent 90-93 Highly recommended.
Very Good 87-89 Often good value; well recommended.
Good 83-86 Suitable for everyday consumption; often good value.
Acceptable 80-82 Can be employed in casual, less-critical circumstances
# function to add rating
rating_category <- function(points){
  if(points>=98){
    return("Classic")
  }
  else if (points>=94){
    return("Superb")
  }
  else if(points>=90){
    return("Excellent")
  }
  else if(points>=87){
    return("Very Good")
  }
  else if(points>=83){
    return("Good")
  }
  else{
    return("Acceptable")
  }
}

wines<- wines %>%
  rowwise() %>%
  mutate(rating_category = rating_category(points))
head(wines)

Add Adjusted Points

Since, each reviewer has a different bias we created a normalized metric, norm_points, by looking at the number of standard deviatioins a wine is from the reviewer’s avg_points. This gives use a more accurate representation of which which wines are better than the rest.

normalize_points <- function(data){
  left_join(data, tasters, by = "taster_name")%>%
    rowwise() %>%
    mutate(norm_points = (points-avg_points)/sd_points) %>%
    select(-avg_points, -sd_points, -var_points, -taster_twitter_handle, -reviews)
}

wines <- normalize_points(wines)
head(wines) 

Data Sanitation

Vintage seems to have year 7200, so we filtered all data upto 2019

wines <- wines %>%
  filter(vintage<2019)

Data Exploration

Univariate Exploration

Alcohol Amount

TODO: EXPLAIN GRAPH AND WHAT WE ARE DOING HERE (OSAKI)

wines %>% 
  group_by(alcohol) %>% 
  ggplot(mapping = aes(x = alcohol)) +
  geom_histogram(na.rm = T,
                 bins = 50) +
  scale_x_continuous(
    name = "Alchohol Percentage", 
    breaks = seq(0,25,1), 
    limits = c(4,22)) +
  scale_fill_fairyfloss() +
  theme_fairyfloss() +
  theme(plot.background = element_rect("white"))
Grouping rowwise data frame strips rowwise nature

Vintage

Understanding what vintage the reivewed wines were from.

wines %>%
  group_by(vintage) %>%
  summarise(count = n())
Grouping rowwise data frame strips rowwise nature

TODO: EXPLAIN GRPAHS (IZZY) (Note: Data points before 1990 have been omitted for clarity in visualization)

wines %>%
  group_by(vintage) %>%
  ggplot() +
  geom_bar(mapping = aes(x=vintage),
           na.rm = T) +
  scale_x_continuous(breaks = seq(1990,2019,5), 
                     limits = c(1990,2019)) +
  labs(x = "Vintage", y = "Count") +
  theme_fairyfloss()
Grouping rowwise data frame strips rowwise nature

Winery

To better understand the number wines per winery, we did a univarite visualization that counts the number of wines per winery showing only 15 winerys to give you an idea what winery has the most selction of wines.

wines %>%
  group_by(winery) %>%
  summarize(count = n()) %>%
  arrange(desc(count)) %>%
  slice(1:15) %>%
  ggplot() +
  geom_col(mapping = aes(x=count, y = reorder(winery, count)))
Grouping rowwise data frame strips rowwise nature

Province

To better understand the number wines per province, we did a univarite visualization that counts the number of wines per province showing only the top 10 provinces with the most wines. This can give the reader an idea where their wine will most likely be made with California standing out as a clear leader.

wines %>% 
  group_by(province) %>% 
  summarize(count = n()) %>% 
  arrange(desc(count)) %>% 
  slice(1:10) %>% 
  ggplot()+
  geom_col(aes(x = count, y = reorder(province, count)))
Grouping rowwise data frame strips rowwise nature

Price

Calculating the Mean, Standard Deviation, Minimum, and Max Price for the entire wine dataset and printing the values.

mean_price <- mean(wines$price, na.rm = TRUE)
sd_price <- sd(wines$price, na.rm = TRUE)
min_price <- min(wines$price, na.rm = TRUE)
max_price <- max(wines$price, na.rm = TRUE)

print(paste("Mean Price:", mean_price))
[1] "Mean Price: 35.4748507788616"
print(paste("SD Price:", sd_price))
[1] "SD Price: 41.238007633635"
print(paste("Min Price:", min_price))
[1] "Min Price: 4"
print(paste("Max Price:", max_price))
[1] "Max Price: 3300"

TODO: Vamsi

wines %>% 
  filter(price < 1000) %>% 
  group_by(price) %>% 
  summarise(count = n()) %>% 
  ggplot() +
  geom_histogram(
    mapping = aes(x=price), 
    na.rm = T) 
Grouping rowwise data frame strips rowwise nature

Points

Calculating the Mean, Standard Deviation, Minimum, and Max Points for the entire wine dataset and printing the values.

mean_points <- mean(wines$points, na.rm = TRUE)
sd_points <- sd(wines$points, na.rm = TRUE)
min_points <- min(wines$points, na.rm = TRUE)
max_points <- max(wines$points, na.rm = TRUE)

print(paste("Mean Points:", mean_points))
[1] "Mean Points: 88.4744820916541"
print(paste("SD Points:", sd_points))
[1] "SD Points: 3.05417480898736"
print(paste("Min Points:", min_points))
[1] "Min Points: 80"
print(paste("Max Points:", max_points))
[1] "Max Points: 100"

TODO: EXPLAIN GRPAH OSAKI

wines %>%
  ggplot() +
  geom_histogram(
    mapping = aes(x=points),
    bins = 20)

Multivariate Exploration

Taster by Points

To help you understand the point distribution by reviewers, we did a multivarite visualization that coorelates some taster names based on the average wine points as identified by the x-intercept. This give you the reader an idea of how some reviewers correlate to the overall average.

wines %>%
  ggplot() +
  geom_boxplot(aes(y=taster_name, x=points)) +
  geom_vline(xintercept = mean(wines$points))

Price by Points

TODO EXPLAIN VAMSI Notice the data is “stacked” and the socres range from 80-100

wines %>% 
  ggplot() +
  geom_point(mapping = (aes(x = points, y = price)), na.rm = T, alpha = 0.15) +
  labs(title = "Price by Points", x = "Points", y = "Price")

TODO EXPLAIN VAMSI

wines %>% 
  ggplot() +
  geom_point(mapping = (aes(x = points, y = log(price))), na.rm = T, alpha = 0.15) +
  labs(title = "log(Price) by Points", x = "Points", y = "log(Price)")

Category

TODO EXPLAIN IZZY This

wines %>% 
  group_by(points) %>% 
  filter(price < 1000) %>% 
  ggplot() +
  geom_point(mapping = aes(x=points, y = price, color = category), 
             na.rm = T) +
  facet_wrap(~ category) +
  scale_color_fairyfloss() +
  theme_minimal() +
  theme(legend.position = "none")
Grouping rowwise data frame strips rowwise nature

Data Analysis

Which province has the best wine?

TODO EXPLAIN OSAKI To determine the best province for wine by points we average all the wines per province and return the top 10 with standard error.

wines %>% 
  group_by(province) %>%
  summarise(avg_points_prov = mean(points), count = n(), std_points_prov_err = sd(points)/sqrt(count)) %>%
  filter(count>30) %>%
  arrange(desc(avg_points_prov)) %>%
  slice(1:10) %>%
  ggplot() +
  geom_col(mapping = aes(y=province, x= avg_points_prov)) +
   geom_errorbar(
    mapping = aes(
      y = province,
      x = avg_points_prov,
      xmin = avg_points_prov - std_points_prov_err, 
      xmax = avg_points_prov + std_points_prov_err
      ),
    width = 0.2
  )
Grouping rowwise data frame strips rowwise nature

Which wine variety is the best?

To determine the best variety of wine we use the average point of all wines per variety with a sample size greater than 30. The graph below shows the the top 10 varieties with their respective standard error.

wines %>% 
  group_by(variety) %>% 
  summarise(
    avg_points_variety = mean(points),
    count = n(),
    sd_err_points_variety = sd(points)/sqrt(count)) %>%
  filter(count>30) %>%
  arrange(desc(avg_points_variety)) %>%
  slice(1:10) %>%
  ggplot() +
  geom_col(mapping = aes(y=variety, x=avg_points_variety))+
  geom_errorbar(
    mapping = aes(
      y = variety,
      x = avg_points_variety,
      xmin = avg_points_variety - sd_err_points_variety, 
      xmax = avg_points_variety + sd_err_points_variety
      ),
    width = 0.2
  )
Grouping rowwise data frame strips rowwise nature

How much are people willing to pay?

TODO IZZY

user_price <- readline(prompt = "How much are you willing to spend on a bottle?")
user_price <- as.integer(user_price)

wines %>% 
  filter(price <= user_price) %>% 
  arrange(desc(points)) %>% 
  select(title, price, points)

What is the best wine?

A easy way to determine the best wine is by simply finding the top 10 wines.

wines %>%
  arrange(desc(points)) %>%
  slice(1:10)

However, this does not account for the graders bias. Instead, our group “normalized” the points based on each taster based on the number of standard deviations an wines is from the raters average. For example, Taster A could give a wine 100 but has an avgerage rating score of 95 with a standard deviation of 5. Whereas, Taster B could give a wine 91 and have an average score of 87 with a standard deviation of 2. Although, the wine tasted by Taster A got a perfect 100 score, Taster B’s wine was much “better” wine since it was 2 standard deviations from the tasters avgerage compared to 1 standard deviation of the other wine.

Looking at the norm_points these are the top 10 best wines

wines %>%
  arrange(desc(norm_points)) %>%
  slice(1:10)

Where are wines produced the most?

Conclusion

Future Works

Works Cited

LS0tCnRpdGxlOiAiRXhwbG9yaW5nIGFuZCBBbmFseWl6aW5nIFdpbmUgRW50aHVzaWFzdCBSZXZpZXdzIgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgdGhlbWU6IHVuaXRlZAotLS0KCiMgUHJlcmVxdWlzaXRlcwoKTG9hZGluZyB0aGUgcmVxdWlyZWQgcGFja2FnZXMKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZ2djdXRlKQpgYGAKCiMgRGF0YXNldAoKSW1wb3J0IHByb2Nlc3NlZCBkYXRhLCB3aGljaCBjYW4gYmUgZm91bmQgW2hlcmVdKGh0dHBzOi8vZ2l0aHViLmNvbS9DNHJieW4zbTRuL3dpbmVfcmV2aWV3c19kYXRhX2FuYWx5c2lzL2Jsb2IvbWFzdGVyL2RhdGEvcHJvY2Vzc2VkX2RhdGEvcHJlcHJvY2Vzc2luZy5ybWQpLgoKYGBge3J9CiNyZWFkIHByZXByb2Nlc3NlZCBkYXRhCndpbmVzIDwtIHJlYWQuY3N2KGZpbGUgPSAnLi4vZGF0YS9wcm9jZXNzZWRfZGF0YS93aW5lcy5jc3YnKQpgYGAKCkdldCBzYW1wbGUgb2YgZGF0YXNldApgYGB7cn0KI3NldCBzZWVkIHZhbHVlIHRvIGJpcnRoZGF5IG9mIFJpY2FyZG8gUm9kcmlndWV6LCBBbWVyaWNhbiB3cmVzdGxlciBhbmQgcmluZyBhbm5vdW5jZXIgYW5kIERyLiBSZWluYWxkbyAoUmVpKSBTYW5jaGV6LUFyaWFzCnNldC5zZWVkKDE5NjMwMjE3KQoKI3NldCBwZXJjZW50YWdlIHRvIHRlc3Qgd2l0aCBmb3Igc2ltcGxpY2l0eSwgaWYgbmVlZGVkCnBlcmNlbnRhZ2UgPC0gNQp3aW5lX3NhbXBsZTwtIHNhbXBsZV9uKHdpbmVzLCBwZXJjZW50YWdlLzEwMCpucm93KHdpbmVzKSkKYGBgCgojIyMgU3BsaXQgVGFzdGVyIGRhdGEgaW50byBkaWZmZXJlbnQgRGF0YSBGcmFtZQoKYGBge3J9CnRhc3RlcnMgPC0gd2luZXMgJT4lCiAgc2VsZWN0KHRhc3Rlcl9uYW1lLCB0YXN0ZXJfdHdpdHRlcl9oYW5kbGUpICU+JSB1bmlxdWUoKQp0YXN0ZXJzCmBgYAoKRHJvcCBgdGFzdGVyX3R3aXR0ZXJfaGFuZGxlYCBpbiB3aW5lcyBkYXRhZnJhbWUKCmBgYHtyfQp3aW5lcyA8LSB3aW5lcyAlPiUKICBzZWxlY3QoLXRhc3Rlcl90d2l0dGVyX2hhbmRsZSkKaGVhZCh3aW5lcykKYGBgCiMjIEFkZCBSZXZpZXdlciBwcm9maWxlIGluZm8KCkVhY2ggcmV2aWV3ZXIgaGFzIHRoZXJlIG93biBiaWFzLiBUbyBvZmZzZXQgdGhhdCB3ZSBtYWRlIGEgInByb2ZpbGUiIGZvciBlYWNoIHJldmlld2VyIHdoaWNoIGluY2x1ZGVzIGNoYXJhY3RlcmlzdGljcyBsaWtlOiBgYXZnX3BvaW50c2AsIGBzZF9wb2ludHNgLCBhbmQgYHZhcl9wb2ludHNgCmBgYHtyfQp0YXN0ZXJfcmF0aW5nX3Byb2ZpbGUgPC0gd2luZXMgJT4lCiAgZ3JvdXBfYnkodGFzdGVyX25hbWUpICU+JQogIHN1bW1hcml6ZSgKICAgIGF2Z19wb2ludHMgPSBtZWFuKHBvaW50cyksCiAgICBzZF9wb2ludHMgPSBzZChwb2ludHMpLAogICAgdmFyX3BvaW50cyA9IHZhcihwb2ludHMpLAogICAgcmV2aWV3cyA9IG4oKQogICkKCnRhc3RlcnMgPC0gaW5uZXJfam9pbih0YXN0ZXJzLCB0YXN0ZXJfcmF0aW5nX3Byb2ZpbGUsIGJ5ID0gInRhc3Rlcl9uYW1lIikKaGVhZCh0YXN0ZXJzKQpgYGAKCiMjIyBBZGQgUmF0aW5nIENsYXNzaWZpY2F0aW9uCgpBZGQgZm9sbG93aW5nIGNsYXNzaWZpY2F0aW9uIHRvIHdpbmUgZGF0YXNldCBhcyBmb3VuZCBvbiB0aGUgW3dlYnNpdGVdKGh0dHBzOi8vd3d3LndpbmVtYWcuY29tLzIwMTAvMDQvMDkveW91LWFza2VkLWhvdy1pcy1hLXdpbmVzLXNjb3JlLWRldGVybWluZWQvKToKCnxDYXRlZ29yeSAgfCBSYXRpbmcgIHwgRGVzY3JpcHRpb24gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfC0tLS0tLS0tLS18LS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfAp8Q2xhc3NpYyAgIHwJOTgtMTAwIHwgVGhlIHBpbm5hY2xlIG9mIHF1YWxpdHkuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfFN1cGVyYiAgICB8CTk0LTk3CSB8IEEgZ3JlYXQgYWNoaWV2ZW1lbnQuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnxFeGNlbGxlbnQgfAk5MC05MwkgfCBIaWdobHkgcmVjb21tZW5kZWQuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8VmVyeSBHb29kIHwgIDg3LTg5CSB8IE9mdGVuIGdvb2QgdmFsdWU7IHdlbGwgcmVjb21tZW5kZWQuICAgICAgICAgICAgICAgICAgICB8CnxHb29kCSAgICAgfCAgODMtODYJIHwgU3VpdGFibGUgZm9yIGV2ZXJ5ZGF5IGNvbnN1bXB0aW9uOyBvZnRlbiBnb29kIHZhbHVlLiAgIHwKfEFjY2VwdGFibGV8CTgwLTgyCSB8IENhbiBiZSBlbXBsb3llZCBpbiBjYXN1YWwsIGxlc3MtY3JpdGljYWwgY2lyY3Vtc3RhbmNlcyB8CgpgYGB7cn0KIyBmdW5jdGlvbiB0byBhZGQgcmF0aW5nCnJhdGluZ19jYXRlZ29yeSA8LSBmdW5jdGlvbihwb2ludHMpewogIGlmKHBvaW50cz49OTgpewogICAgcmV0dXJuKCJDbGFzc2ljIikKICB9CiAgZWxzZSBpZiAocG9pbnRzPj05NCl7CiAgICByZXR1cm4oIlN1cGVyYiIpCiAgfQogIGVsc2UgaWYocG9pbnRzPj05MCl7CiAgICByZXR1cm4oIkV4Y2VsbGVudCIpCiAgfQogIGVsc2UgaWYocG9pbnRzPj04Nyl7CiAgICByZXR1cm4oIlZlcnkgR29vZCIpCiAgfQogIGVsc2UgaWYocG9pbnRzPj04Myl7CiAgICByZXR1cm4oIkdvb2QiKQogIH0KICBlbHNlewogICAgcmV0dXJuKCJBY2NlcHRhYmxlIikKICB9Cn0KCndpbmVzPC0gd2luZXMgJT4lCiAgcm93d2lzZSgpICU+JQogIG11dGF0ZShyYXRpbmdfY2F0ZWdvcnkgPSByYXRpbmdfY2F0ZWdvcnkocG9pbnRzKSkKaGVhZCh3aW5lcykKYGBgCgojIyBBZGQgQWRqdXN0ZWQgUG9pbnRzCgpTaW5jZSwgZWFjaCByZXZpZXdlciBoYXMgYSBkaWZmZXJlbnQgYmlhcyB3ZSBjcmVhdGVkIGEgbm9ybWFsaXplZCBtZXRyaWMsIGBub3JtX3BvaW50c2AsIGJ5IGxvb2tpbmcgYXQgdGhlIG51bWJlciBvZiBzdGFuZGFyZCBkZXZpYXRpb2lucyBhIHdpbmUgaXMgZnJvbSB0aGUgcmV2aWV3ZXIncyBgYXZnX3BvaW50c2AuIFRoaXMgZ2l2ZXMgdXNlIGEgbW9yZSBhY2N1cmF0ZSByZXByZXNlbnRhdGlvbiBvZiB3aGljaCB3aGljaCB3aW5lcyBhcmUgYmV0dGVyIHRoYW4gdGhlIHJlc3QuCgpgYGB7cn0Kbm9ybWFsaXplX3BvaW50cyA8LSBmdW5jdGlvbihkYXRhKXsKICBsZWZ0X2pvaW4oZGF0YSwgdGFzdGVycywgYnkgPSAidGFzdGVyX25hbWUiKSU+JQogICAgcm93d2lzZSgpICU+JQogICAgbXV0YXRlKG5vcm1fcG9pbnRzID0gKHBvaW50cy1hdmdfcG9pbnRzKS9zZF9wb2ludHMpICU+JQogICAgc2VsZWN0KC1hdmdfcG9pbnRzLCAtc2RfcG9pbnRzLCAtdmFyX3BvaW50cywgLXRhc3Rlcl90d2l0dGVyX2hhbmRsZSwgLXJldmlld3MpCn0KCndpbmVzIDwtIG5vcm1hbGl6ZV9wb2ludHMod2luZXMpCmhlYWQod2luZXMpIApgYGAKCiMjIERhdGEgU2FuaXRhdGlvbgpWaW50YWdlIHNlZW1zIHRvIGhhdmUgeWVhciA3MjAwLCBzbyB3ZSBmaWx0ZXJlZCBhbGwgZGF0YSB1cHRvIDIwMTkKYGBgIHtyfQp3aW5lcyA8LSB3aW5lcyAlPiUKICBmaWx0ZXIodmludGFnZTwyMDE5KQpgYGAKIyBEYXRhIEV4cGxvcmF0aW9uCgojIyBVbml2YXJpYXRlIEV4cGxvcmF0aW9uCiMjIyBBbGNvaG9sIEFtb3VudApUT0RPOiBFWFBMQUlOIEdSQVBIIEFORCBXSEFUIFdFIEFSRSBET0lORyBIRVJFIChPU0FLSSkKYGBge3J9CndpbmVzICU+JSAKICBncm91cF9ieShhbGNvaG9sKSAlPiUgCiAgZ2dwbG90KG1hcHBpbmcgPSBhZXMoeCA9IGFsY29ob2wpKSArCiAgZ2VvbV9oaXN0b2dyYW0obmEucm0gPSBULAogICAgICAgICAgICAgICAgIGJpbnMgPSA1MCkgKwogIHNjYWxlX3hfY29udGludW91cygKICAgIG5hbWUgPSAiQWxjaG9ob2wgUGVyY2VudGFnZSIsIAogICAgYnJlYWtzID0gc2VxKDAsMjUsMSksIAogICAgbGltaXRzID0gYyg0LDIyKSkgKwogIHNjYWxlX2ZpbGxfZmFpcnlmbG9zcygpICsKICB0aGVtZV9mYWlyeWZsb3NzKCkgKwogIHRoZW1lKHBsb3QuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdCgid2hpdGUiKSkKYGBgCgojIyMgVmludGFnZQpVbmRlcnN0YW5kaW5nIHdoYXQgdmludGFnZSB0aGUgcmVpdmV3ZWQgd2luZXMgd2VyZSBmcm9tLgpgYGB7cn0Kd2luZXMgJT4lCiAgZ3JvdXBfYnkodmludGFnZSkgJT4lCiAgc3VtbWFyaXNlKGNvdW50ID0gbigpKQpgYGAKCgpUT0RPOiBFWFBMQUlOIEdSUEFIUyAoSVpaWSkKKE5vdGU6IERhdGEgcG9pbnRzIGJlZm9yZSAxOTkwIGhhdmUgYmVlbiBvbWl0dGVkIGZvciBjbGFyaXR5IGluIHZpc3VhbGl6YXRpb24pCmBgYHtyfQp3aW5lcyAlPiUKICBncm91cF9ieSh2aW50YWdlKSAlPiUKICBnZ3Bsb3QoKSArCiAgZ2VvbV9iYXIobWFwcGluZyA9IGFlcyh4PXZpbnRhZ2UpLAogICAgICAgICAgIG5hLnJtID0gVCkgKwogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMTk5MCwyMDE5LDUpLCAKICAgICAgICAgICAgICAgICAgICAgbGltaXRzID0gYygxOTkwLDIwMTkpKSArCiAgbGFicyh4ID0gIlZpbnRhZ2UiLCB5ID0gIkNvdW50IikgKwogIHRoZW1lX2ZhaXJ5Zmxvc3MoKQpgYGAKCiMjIyBXaW5lcnkKVG8gYmV0dGVyIHVuZGVyc3RhbmQgdGhlIG51bWJlciB3aW5lcyBwZXIgd2luZXJ5LCB3ZSBkaWQgYSB1bml2YXJpdGUgdmlzdWFsaXphdGlvbiB0aGF0IGNvdW50cyB0aGUgbnVtYmVyIG9mIHdpbmVzIHBlciB3aW5lcnkgc2hvd2luZyBvbmx5IDE1IHdpbmVyeXMgdG8gZ2l2ZSB5b3UgYW4gaWRlYSB3aGF0IHdpbmVyeSBoYXMgdGhlIG1vc3Qgc2VsY3Rpb24gb2Ygd2luZXMuCmBgYHtyfQp3aW5lcyAlPiUKICBncm91cF9ieSh3aW5lcnkpICU+JQogIHN1bW1hcml6ZShjb3VudCA9IG4oKSkgJT4lCiAgYXJyYW5nZShkZXNjKGNvdW50KSkgJT4lCiAgc2xpY2UoMToxNSkgJT4lCiAgZ2dwbG90KCkgKwogIGdlb21fY29sKG1hcHBpbmcgPSBhZXMoeD1jb3VudCwgeSA9IHJlb3JkZXIod2luZXJ5LCBjb3VudCkpKQpgYGAKCiMjIyBQcm92aW5jZQpUbyBiZXR0ZXIgdW5kZXJzdGFuZCB0aGUgbnVtYmVyIHdpbmVzIHBlciBwcm92aW5jZSwgd2UgZGlkIGEgdW5pdmFyaXRlIHZpc3VhbGl6YXRpb24gdGhhdCBjb3VudHMgdGhlIG51bWJlciBvZiB3aW5lcyBwZXIgcHJvdmluY2Ugc2hvd2luZyBvbmx5IHRoZSB0b3AgMTAgcHJvdmluY2VzIHdpdGggdGhlIG1vc3Qgd2luZXMuIFRoaXMgY2FuIGdpdmUgdGhlIHJlYWRlciBhbiBpZGVhIHdoZXJlIHRoZWlyIHdpbmUgd2lsbCBtb3N0IGxpa2VseSBiZSBtYWRlIHdpdGggQ2FsaWZvcm5pYSBzdGFuZGluZyBvdXQgYXMgYSBjbGVhciBsZWFkZXIuCmBgYHtyfQp3aW5lcyAlPiUgCiAgZ3JvdXBfYnkocHJvdmluY2UpICU+JSAKICBzdW1tYXJpemUoY291bnQgPSBuKCkpICU+JSAKICBhcnJhbmdlKGRlc2MoY291bnQpKSAlPiUgCiAgc2xpY2UoMToxMCkgJT4lIAogIGdncGxvdCgpKwogIGdlb21fY29sKGFlcyh4ID0gY291bnQsIHkgPSByZW9yZGVyKHByb3ZpbmNlLCBjb3VudCkpKQpgYGAKCiMjIyBQcmljZQpDYWxjdWxhdGluZyB0aGUgTWVhbiwgU3RhbmRhcmQgRGV2aWF0aW9uLCBNaW5pbXVtLCBhbmQgTWF4IFByaWNlIGZvciB0aGUgZW50aXJlIHdpbmUgZGF0YXNldCBhbmQgcHJpbnRpbmcgdGhlIHZhbHVlcy4KYGBge3J9Cm1lYW5fcHJpY2UgPC0gbWVhbih3aW5lcyRwcmljZSwgbmEucm0gPSBUUlVFKQpzZF9wcmljZSA8LSBzZCh3aW5lcyRwcmljZSwgbmEucm0gPSBUUlVFKQptaW5fcHJpY2UgPC0gbWluKHdpbmVzJHByaWNlLCBuYS5ybSA9IFRSVUUpCm1heF9wcmljZSA8LSBtYXgod2luZXMkcHJpY2UsIG5hLnJtID0gVFJVRSkKCnByaW50KHBhc3RlKCJNZWFuIFByaWNlOiIsIG1lYW5fcHJpY2UpKQpwcmludChwYXN0ZSgiU0QgUHJpY2U6Iiwgc2RfcHJpY2UpKQpwcmludChwYXN0ZSgiTWluIFByaWNlOiIsIG1pbl9wcmljZSkpCnByaW50KHBhc3RlKCJNYXggUHJpY2U6IiwgbWF4X3ByaWNlKSkKYGBgCgpUT0RPOiBWYW1zaQpgYGB7cn0Kd2luZXMgJT4lIAogIGZpbHRlcihwcmljZSA8IDEwMDApICU+JSAKICBncm91cF9ieShwcmljZSkgJT4lIAogIHN1bW1hcmlzZShjb3VudCA9IG4oKSkgJT4lIAogIGdncGxvdCgpICsKICBnZW9tX2hpc3RvZ3JhbSgKICAgIG1hcHBpbmcgPSBhZXMoeD1wcmljZSksIAogICAgbmEucm0gPSBUKSAKYGBgCgojIyMgIFBvaW50cyAKQ2FsY3VsYXRpbmcgdGhlIE1lYW4sIFN0YW5kYXJkIERldmlhdGlvbiwgTWluaW11bSwgYW5kIE1heCBQb2ludHMgZm9yIHRoZSBlbnRpcmUgd2luZSBkYXRhc2V0IGFuZCBwcmludGluZyB0aGUgdmFsdWVzLgpgYGB7cn0KbWVhbl9wb2ludHMgPC0gbWVhbih3aW5lcyRwb2ludHMsIG5hLnJtID0gVFJVRSkKc2RfcG9pbnRzIDwtIHNkKHdpbmVzJHBvaW50cywgbmEucm0gPSBUUlVFKQptaW5fcG9pbnRzIDwtIG1pbih3aW5lcyRwb2ludHMsIG5hLnJtID0gVFJVRSkKbWF4X3BvaW50cyA8LSBtYXgod2luZXMkcG9pbnRzLCBuYS5ybSA9IFRSVUUpCgpwcmludChwYXN0ZSgiTWVhbiBQb2ludHM6IiwgbWVhbl9wb2ludHMpKQpwcmludChwYXN0ZSgiU0QgUG9pbnRzOiIsIHNkX3BvaW50cykpCnByaW50KHBhc3RlKCJNaW4gUG9pbnRzOiIsIG1pbl9wb2ludHMpKQpwcmludChwYXN0ZSgiTWF4IFBvaW50czoiLCBtYXhfcG9pbnRzKSkKYGBgClRPRE86IEVYUExBSU4gR1JQQUggT1NBS0kKYGBge3J9CndpbmVzICU+JQogIGdncGxvdCgpICsKICBnZW9tX2hpc3RvZ3JhbSgKICAgIG1hcHBpbmcgPSBhZXMoeD1wb2ludHMpLAogICAgYmlucyA9IDIwKQpgYGAKCiMjIE11bHRpdmFyaWF0ZSBFeHBsb3JhdGlvbgoKIyMgVGFzdGVyIGJ5IFBvaW50cwpUbyBoZWxwIHlvdSB1bmRlcnN0YW5kIHRoZSBwb2ludCBkaXN0cmlidXRpb24gYnkgcmV2aWV3ZXJzLCB3ZSBkaWQgYSBtdWx0aXZhcml0ZSB2aXN1YWxpemF0aW9uIHRoYXQgY29vcmVsYXRlcyBzb21lIHRhc3RlciBuYW1lcyBiYXNlZCBvbiB0aGUgYXZlcmFnZSB3aW5lIHBvaW50cyBhcyBpZGVudGlmaWVkICBieSB0aGUgeC1pbnRlcmNlcHQuIFRoaXMgZ2l2ZSB5b3UgdGhlIHJlYWRlciBhbiBpZGVhIG9mIGhvdyBzb21lIHJldmlld2VycyBjb3JyZWxhdGUgdG8gdGhlIG92ZXJhbGwgYXZlcmFnZS4KYGBge3J9CndpbmVzICU+JQogIGdncGxvdCgpICsKICBnZW9tX2JveHBsb3QoYWVzKHk9dGFzdGVyX25hbWUsIHg9cG9pbnRzKSkgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IG1lYW4od2luZXMkcG9pbnRzKSkKYGBgCgoKIyMgUHJpY2UgYnkgUG9pbnRzClRPRE8gRVhQTEFJTiBWQU1TSQpOb3RpY2UgdGhlIGRhdGEgaXMgInN0YWNrZWQiIGFuZCB0aGUgc29jcmVzIHJhbmdlIGZyb20gODAtMTAwCmBgYHtyfQp3aW5lcyAlPiUgCiAgZ2dwbG90KCkgKwogIGdlb21fcG9pbnQobWFwcGluZyA9IChhZXMoeCA9IHBvaW50cywgeSA9IHByaWNlKSksIG5hLnJtID0gVCwgYWxwaGEgPSAwLjE1KSArCiAgbGFicyh0aXRsZSA9ICJQcmljZSBieSBQb2ludHMiLCB4ID0gIlBvaW50cyIsIHkgPSAiUHJpY2UiKQpgYGAKClRPRE8gRVhQTEFJTiBWQU1TSQpgYGB7cn0Kd2luZXMgJT4lIAogIGdncGxvdCgpICsKICBnZW9tX3BvaW50KG1hcHBpbmcgPSAoYWVzKHggPSBwb2ludHMsIHkgPSBsb2cocHJpY2UpKSksIG5hLnJtID0gVCwgYWxwaGEgPSAwLjE1KSArCiAgbGFicyh0aXRsZSA9ICJsb2coUHJpY2UpIGJ5IFBvaW50cyIsIHggPSAiUG9pbnRzIiwgeSA9ICJsb2coUHJpY2UpIikKYGBgCgojIyMgQ2F0ZWdvcnkKVE9ETyBFWFBMQUlOIElaWlkKVGhpcyAKYGBge3J9CndpbmVzICU+JSAKICBncm91cF9ieShwb2ludHMpICU+JSAKICBmaWx0ZXIocHJpY2UgPCAxMDAwKSAlPiUgCiAgZ2dwbG90KCkgKwogIGdlb21fcG9pbnQobWFwcGluZyA9IGFlcyh4PXBvaW50cywgeSA9IHByaWNlLCBjb2xvciA9IGNhdGVnb3J5KSwgCiAgICAgICAgICAgICBuYS5ybSA9IFQpICsKICBmYWNldF93cmFwKH4gY2F0ZWdvcnkpICsKICBzY2FsZV9jb2xvcl9mYWlyeWZsb3NzKCkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQpgYGAKCgoKIyBEYXRhIEFuYWx5c2lzCgojIyBXaGljaCBwcm92aW5jZSBoYXMgdGhlIGJlc3Qgd2luZT8KVE9ETyBFWFBMQUlOIE9TQUtJClRvIGRldGVybWluZSB0aGUgYmVzdCBwcm92aW5jZSBmb3Igd2luZSBieSBwb2ludHMgd2UgYXZlcmFnZSBhbGwgdGhlIHdpbmVzIHBlciBwcm92aW5jZSBhbmQgcmV0dXJuIHRoZSB0b3AgMTAgd2l0aCBzdGFuZGFyZCBlcnJvci4KYGBge3J9CndpbmVzICU+JSAKICBncm91cF9ieShwcm92aW5jZSkgJT4lCiAgc3VtbWFyaXNlKGF2Z19wb2ludHNfcHJvdiA9IG1lYW4ocG9pbnRzKSwgY291bnQgPSBuKCksIHN0ZF9wb2ludHNfcHJvdl9lcnIgPSBzZChwb2ludHMpL3NxcnQoY291bnQpKSAlPiUKICBmaWx0ZXIoY291bnQ+MzApICU+JQogIGFycmFuZ2UoZGVzYyhhdmdfcG9pbnRzX3Byb3YpKSAlPiUKICBzbGljZSgxOjEwKSAlPiUKICBnZ3Bsb3QoKSArCiAgZ2VvbV9jb2wobWFwcGluZyA9IGFlcyh5PXByb3ZpbmNlLCB4PSBhdmdfcG9pbnRzX3Byb3YpKSArCiAgIGdlb21fZXJyb3JiYXIoCiAgICBtYXBwaW5nID0gYWVzKAogICAgICB5ID0gcHJvdmluY2UsCiAgICAgIHggPSBhdmdfcG9pbnRzX3Byb3YsCiAgICAgIHhtaW4gPSBhdmdfcG9pbnRzX3Byb3YgLSBzdGRfcG9pbnRzX3Byb3ZfZXJyLCAKICAgICAgeG1heCA9IGF2Z19wb2ludHNfcHJvdiArIHN0ZF9wb2ludHNfcHJvdl9lcnIKICAgICAgKSwKICAgIHdpZHRoID0gMC4yCiAgKQpgYGAKCiMjIFdoaWNoIHdpbmUgdmFyaWV0eSBpcyB0aGUgYmVzdD8KVG8gZGV0ZXJtaW5lIHRoZSBiZXN0IHZhcmlldHkgb2Ygd2luZSB3ZSB1c2UgdGhlIGF2ZXJhZ2UgcG9pbnQgb2YgYWxsIHdpbmVzIHBlciB2YXJpZXR5IHdpdGggYSBzYW1wbGUgc2l6ZSBncmVhdGVyIHRoYW4gMzAuIFRoZSBncmFwaCBiZWxvdyBzaG93cyB0aGUgdGhlIHRvcCAxMCB2YXJpZXRpZXMgd2l0aCB0aGVpciByZXNwZWN0aXZlIHN0YW5kYXJkIGVycm9yLgoKYGBge3J9CndpbmVzICU+JSAKICBncm91cF9ieSh2YXJpZXR5KSAlPiUgCiAgc3VtbWFyaXNlKAogICAgYXZnX3BvaW50c192YXJpZXR5ID0gbWVhbihwb2ludHMpLAogICAgY291bnQgPSBuKCksCiAgICBzZF9lcnJfcG9pbnRzX3ZhcmlldHkgPSBzZChwb2ludHMpL3NxcnQoY291bnQpKSAlPiUKICBmaWx0ZXIoY291bnQ+MzApICU+JQogIGFycmFuZ2UoZGVzYyhhdmdfcG9pbnRzX3ZhcmlldHkpKSAlPiUKICBzbGljZSgxOjEwKSAlPiUKICBnZ3Bsb3QoKSArCiAgZ2VvbV9jb2wobWFwcGluZyA9IGFlcyh5PXZhcmlldHksIHg9YXZnX3BvaW50c192YXJpZXR5KSkrCiAgZ2VvbV9lcnJvcmJhcigKICAgIG1hcHBpbmcgPSBhZXMoCiAgICAgIHkgPSB2YXJpZXR5LAogICAgICB4ID0gYXZnX3BvaW50c192YXJpZXR5LAogICAgICB4bWluID0gYXZnX3BvaW50c192YXJpZXR5IC0gc2RfZXJyX3BvaW50c192YXJpZXR5LCAKICAgICAgeG1heCA9IGF2Z19wb2ludHNfdmFyaWV0eSArIHNkX2Vycl9wb2ludHNfdmFyaWV0eQogICAgICApLAogICAgd2lkdGggPSAwLjIKICApCmBgYAoKIyMgSG93IG11Y2ggYXJlIHBlb3BsZSB3aWxsaW5nIHRvIHBheT8KVE9ETyBJWlpZCmBgYHtyfQp1c2VyX3ByaWNlIDwtIHJlYWRsaW5lKHByb21wdCA9ICJIb3cgbXVjaCBhcmUgeW91IHdpbGxpbmcgdG8gc3BlbmQgb24gYSBib3R0bGU/IikKdXNlcl9wcmljZSA8LSBhcy5pbnRlZ2VyKHVzZXJfcHJpY2UpCgp3aW5lcyAlPiUgCiAgZmlsdGVyKHByaWNlIDw9IHVzZXJfcHJpY2UpICU+JSAKICBhcnJhbmdlKGRlc2MocG9pbnRzKSkgJT4lIAogIHNlbGVjdCh0aXRsZSwgcHJpY2UsIHBvaW50cykKYGBgCgojIyBXaGF0IGlzIHRoZSBiZXN0IHdpbmU/CkEgZWFzeSB3YXkgdG8gZGV0ZXJtaW5lIHRoZSBiZXN0IHdpbmUgaXMgYnkgc2ltcGx5IGZpbmRpbmcgdGhlIHRvcCAxMCB3aW5lcy4KYGBge3J9CndpbmVzICU+JQogIGFycmFuZ2UoZGVzYyhwb2ludHMpKSAlPiUKICBzbGljZSgxOjEwKQpgYGAKCkhvd2V2ZXIsIHRoaXMgZG9lcyBub3QgYWNjb3VudCBmb3IgdGhlIGdyYWRlcnMgYmlhcy4gSW5zdGVhZCwgb3VyIGdyb3VwICJub3JtYWxpemVkIiB0aGUgcG9pbnRzIGJhc2VkIG9uIGVhY2ggdGFzdGVyIGJhc2VkIG9uIHRoZSBudW1iZXIgb2Ygc3RhbmRhcmQgZGV2aWF0aW9ucyBhbiB3aW5lcyBpcyBmcm9tIHRoZSByYXRlcnMgYXZlcmFnZS4gRm9yIGV4YW1wbGUsIFRhc3RlciBBIGNvdWxkIGdpdmUgYSB3aW5lIDEwMCBidXQgaGFzIGFuIGF2Z2VyYWdlIHJhdGluZyBzY29yZSBvZiA5NSB3aXRoIGEgc3RhbmRhcmQgZGV2aWF0aW9uIG9mIDUuIFdoZXJlYXMsIFRhc3RlciBCIGNvdWxkIGdpdmUgYSB3aW5lIDkxIGFuZCBoYXZlIGFuIGF2ZXJhZ2Ugc2NvcmUgb2YgODcgd2l0aCBhIHN0YW5kYXJkIGRldmlhdGlvbiBvZiAyLiBBbHRob3VnaCwgdGhlIHdpbmUgdGFzdGVkIGJ5IFRhc3RlciBBIGdvdCBhIHBlcmZlY3QgMTAwIHNjb3JlLCBUYXN0ZXIgQidzIHdpbmUgd2FzIG11Y2ggImJldHRlciIgd2luZSBzaW5jZSBpdCB3YXMgMiBzdGFuZGFyZCBkZXZpYXRpb25zIGZyb20gdGhlIHRhc3RlcnMgYXZnZXJhZ2UgY29tcGFyZWQgdG8gMSBzdGFuZGFyZCBkZXZpYXRpb24gb2YgdGhlIG90aGVyIHdpbmUuCgpMb29raW5nIGF0IHRoZSBgbm9ybV9wb2ludHNgIHRoZXNlIGFyZSB0aGUgdG9wIDEwIGJlc3Qgd2luZXMKYGBge3J9CndpbmVzICU+JQogIGFycmFuZ2UoZGVzYyhub3JtX3BvaW50cykpICU+JQogIHNsaWNlKDE6MTApCmBgYAojIyBXaGVyZSBhcmUgd2luZXMgcHJvZHVjZWQgdGhlIG1vc3Q/CgoKIyBDb25jbHVzaW9uCgojIEZ1dHVyZSBXb3JrcwoKIyBXb3JrcyBDaXRlZAo=